package com.gitee.qdbp.general.thirdpart.biz.amap.service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.gitee.qdbp.able.exception.ServiceException;
import com.gitee.qdbp.able.result.ResultCode;
import com.gitee.qdbp.base.model.consts.Keys;
import com.gitee.qdbp.general.common.api.settings.model.AreaBean;
import com.gitee.qdbp.general.common.api.settings.service.IAreaDivisionQueryer;
import com.gitee.qdbp.general.common.biz.settings.service.AreaDivisionTools;
import com.gitee.qdbp.general.common.enums.AreaType;
import com.gitee.qdbp.general.thirdpart.api.map.model.IpLocation;
import com.gitee.qdbp.general.thirdpart.api.map.model.Point;
import com.gitee.qdbp.general.thirdpart.api.map.model.SuggestionDetails;
import com.gitee.qdbp.general.thirdpart.api.map.model.TownshipDetails;
import com.gitee.qdbp.general.thirdpart.api.map.service.IMapService;
import com.gitee.qdbp.general.thirdpart.biz.amap.model.AmapConfig;
import com.gitee.qdbp.general.thirdpart.error.MapErrorCode;
import com.gitee.qdbp.tools.http.HttpException;
import com.gitee.qdbp.tools.utils.ConvertTools;
import com.gitee.qdbp.tools.utils.StringTools;
import com.gitee.qdbp.tools.utils.VerifyTools;

/**
 * 高德地图<br>
 * https://lbs.amap.com/api/webservice/summary
 *
 * @author zhaohuihua
 * @version 180717
 */
public class AmapService implements IMapService {

    private static Logger log = LoggerFactory.getLogger(AmapService.class);

    private AmapHttpExecutor httpExecutor;
    @Autowired
    protected AmapConfig config;
    @Autowired
    private IAreaDivisionQueryer areaDivisionQueryer;

    @PostConstruct
    protected void init() {
        httpExecutor = new AmapHttpExecutor(config);
    }

    /** IP定位 **/
    // https://lbs.amap.com/api/webservice/guide/api/ipconfig
    @Override
    public IpLocation ipLocating(String ip) throws ServiceException {
        Map<String, Object> params = new HashMap<>();
        params.put("ip", ip);

        // "province" : "江苏省",
        // "city" : "南京市",
        // "adcode" : "320100",
        // "rectangle" : "118.4253323,31.80452471;119.050169,32.39401346"
        JSONObject json;
        try {
            json = httpExecutor.query("amap.ip.locating", params, JSONObject.class);
        } catch (HttpException e) {
            log.error("Failed to invoke amap interface", e);
            throw new ServiceException(ResultCode.REMOTE_SERVICE_ERROR, e);
        }
        if (json == null) {
            return null;
        }

        String areaCode = json.getString("adcode");
        String province = json.getString("province");
        String city = json.getString("city");
        String rectangle = json.getString("rectangle");
        if (VerifyTools.isAnyBlank(areaCode, province, city, rectangle)) {
            return null;
        }

        String areaText = province.equals(city) ? province : province + " " + city;
        String location = calculateCenterPoint(rectangle);

        IpLocation result = new IpLocation();
        result.setAreaCode(areaCode);
        result.setAreaText(areaText);
        result.setLocation(location);
        return result;
    }

    /** 根据用户输入的关键字给出地址提示 **/
    // https://lbs.amap.com/api/webservice/guide/api/inputtips
    // v3/assistant/inputtips?keywords=银行&location=116.481488,39.990464&output=JSON&key=<KEY>
    @Override
    public List<SuggestionDetails> suggestion(String keyword, String location) throws ServiceException {

        Map<String, Object> params = new HashMap<>();
        params.put("keywords", keyword);
        params.put("location", location);

        // "id" : "B000A7BM4H",
        // "name" : "肯德基(花家地店)",
        // "district" : "北京市朝阳区",
        // "adcode" : "110105",
        // "location" : "116.469271,39.985568",
        // "address" : "花家地小区1号商业楼",
        // "typecode" : "050301"
        JSONArray array;
        try {
            array = httpExecutor.query("amap.assistant.inputtips", params, JSONArray.class);
        } catch (HttpException e) {
            log.error("Failed to invoke amap interface", e);
            throw new ServiceException(ResultCode.REMOTE_SERVICE_ERROR, e);
        }
        if (VerifyTools.isBlank(array)) {
            return null;
        }

        List<SuggestionDetails> list = new ArrayList<>();
        for (int i = 0; i < array.size(); i++) {
            JSONObject json = array.getJSONObject(i);
            String placeName = json.getString("name");
            String loc = json.getString("location");
            String areaCode = json.getString("adcode");
            if (VerifyTools.isBlank(placeName) && VerifyTools.isAnyBlank(loc, areaCode)) {
                continue; // 名称必须有, location或areaCode必有其一
            }
            SuggestionDetails details = new SuggestionDetails();
            details.setPlaceName(placeName);
            details.setLocation(loc);
            details.setAreaCode(areaCode);
            details.setAreaText(json.getString("district"));
            details.setAddress(json.getString("address"));
            list.add(details);
        }
        return list.isEmpty() ? null : list;
    }

    /** 根据经纬度查询乡镇街道级信息 **/
    // https://lbs.amap.com/api/webservice/guide/api/georegeo
    // https://restapi.amap.com/v3/geocode/regeo?location=116.310003,39.991957&radius=10&extensions=base&output=JSON&key=<KEY>
    @Override
    public TownshipDetails township(String scene, String location) throws ServiceException {

        Map<String, Object> params = new HashMap<>();
        params.put("location", location);
        params.put("radius", 10);
        params.put("extensions", "base");

        // "country" : "中国",
        // "province" : "安徽省",
        // "city" : "合肥市",
        // "citycode" : "0551",
        // "district" : "蜀山区",
        // "adcode" : "340104",
        // "township" : "蜀山新产业园区",
        // "towncode" : "340104400000"
        JSONObject json;
        try {
            json = httpExecutor.query("amap.geocode.regeo", params, JSONObject.class);
        } catch (HttpException e) {
            log.error("Failed to invoke amap interface", e);
            throw new ServiceException(ResultCode.REMOTE_SERVICE_ERROR, e);
        }
        if (json == null) {
            return null;
        }

        String areaCode = json.getString("adcode");
        String areaName = json.getString("district");
        String township = json.getString("township");
        if (VerifyTools.isBlank(areaCode)) {
            return null;
        }

        String areaText;
        {
            AreaBean areaDbBean = areaDivisionQueryer.findByCode(scene, areaCode);
            if (areaDbBean == null) {
                log.error("Db area division not found, areaCode={}", areaCode);
                throw new ServiceException(MapErrorCode.AREA_DIVISION_HAS_EXPIRED);
            }
            String dbAreaName = areaDbBean.getAreaName();
            if (!areaNameEquals(areaName, dbAreaName)) {
                log.error("AreaName not equals DbAreaName, areaCode={}, {}--{}", areaCode, areaName, dbAreaName);
                throw new ServiceException(MapErrorCode.AREA_DIVISION_HAS_EXPIRED);
            }
            areaText = areaDivisionQueryer.findAreaText(scene, areaCode);
        }

        TownshipDetails details = new TownshipDetails();
        details.setAreaCode(areaCode);
        details.setAreaText(areaText);
        details.setTownshipName(township);
        return details;
    }

    /** 查询行政区划 **/
    // https://lbs.amap.com/api/webservice/guide/api/district
    // v3/config/district
    @Override
    public List<AreaBean> queryAreaDivisions() {

        Map<String, Object> params = new HashMap<>();
        params.put("subdistrict", 3);
        params.put("extensions", "base");

        // "citycode" : "021",
        // "adcode" : "310000",
        // "name" : "上海市",
        // "center" : "121.472644,31.231706",
        // "level" : "province",
        // "districts" : []
        JSONArray array;
        try {
            array = httpExecutor.query("amap.config.district", params, JSONArray.class);
        } catch (HttpException e) {
            log.error("Failed to invoke amap interface", e);
            throw new ServiceException(ResultCode.REMOTE_SERVICE_ERROR, e);
        }
        if (VerifyTools.isBlank(array)) {
            return null;
        }

        List<AreaBean> list = new ArrayList<>();
        collectAreaDivision(list, null, array);
        Collections.sort(list);
        return list.isEmpty() ? null : list;
    }

    private static void collectAreaDivision(List<AreaBean> list, AreaBean parent, JSONArray array) {
        for (int i = 0; i < array.size(); i++) {
            JSONObject json = array.getJSONObject(i);
            String areaCode = json.getString("adcode");
            String areaName = json.getString("name");
            String level = json.getString("level");
            JSONArray children = json.getJSONArray("districts");
            AreaType areaType = parseAreaType(level);

            if (parent != null && areaType == AreaType.CITY && areaName.endsWith("城区")) {
                // 北京城区改为北京市
                areaName = parent.getAreaName();
            }

            AreaBean area = new AreaBean();
            area.setAreaCode(areaCode);
            area.setAreaName(areaName);
            area.setType(areaType);
            area.setParentCode(parent == null ? Keys.ROOT_CODE : parent.getAreaCode());
            AreaDivisionTools.fillShortName(area, false);
            AreaDivisionTools.fillSortIndex(area, false);
            if (areaType != null && areaType != AreaType.TOWNSHIP) {
                // 有一些没有CITY这一级: 河南(410000)的济源市(419001); 海南(460000)的五指山市(469001),琼海市(469002)
                // 有一些没有DISTRICT这一级: 广东(440000)的东莞市(441900), 下面直接到TOWNSHIP
                // 但是高德没有对TOWNSHIP这一级分配编号, TOWNSHIP的编号等于上级编号
                list.add(area);
            }

            if (VerifyTools.isNotBlank(children)) {
                collectAreaDivision(list, area, children);
            }
        }
    }

    private static AreaType parseAreaType(String type) {
        if ("country".equals(type)) {
            return AreaType.COUNTRY;
        } else if ("province".equals(type)) {
            return AreaType.PROVINCE;
        } else if ("city".equals(type)) {
            return AreaType.CITY;
        } else if ("district".equals(type)) {
            return AreaType.DISTRICT;
        } else if ("street".equals(type)) {
            return AreaType.TOWNSHIP;
        } else {
            return null;
        }
    }

    private static boolean areaNameEquals(String areaName, String dbAreaName) {
        String prefix = areaName.length() > 2 ? areaName.substring(0, 2) : areaName;
        return dbAreaName.startsWith(prefix);
    }

    private static String calculateCenterPoint(String points) {
        if (!points.contains(";")) {
            return points;
        }
        String[] array = StringTools.split(points, ';');
        List<Point> list = new ArrayList<>();
        for (String string : array) {
            int index = string.indexOf(',');
            if (index <= 0) {
                throw new IllegalArgumentException("comma not found in point string: [" + string + "]");
            }
            String longitude = string.substring(0, index);
            String latitude = string.substring(index + 1);
            Point point = new Point(ConvertTools.toDouble(longitude), ConvertTools.toDouble(latitude));
            list.add(point);
        }
        Point center = calculateCenterPoint(list);
        return center.getLongitude() + "," + center.getLatitude();
    }

    // 计算中心点
    // https://blog.csdn.net/yl2isoft/article/details/16368397
    private static Point calculateCenterPoint(List<Point> points) {
        int total = points.size();
        double x = 0, y = 0, z = 0;
        for (Point g : points) {
            double lat, lon;
            lon = g.getLongitude() * Math.PI / 180;
            lat = g.getLatitude() * Math.PI / 180;
            x += Math.cos(lat) * Math.cos(lon);
            y += Math.cos(lat) * Math.sin(lon);
            z += Math.sin(lat);
        }
        x = x / total;
        y = y / total;
        z = z / total;
        double lon = Math.atan2(y, x);
        double hyp = Math.sqrt(x * x + y * y);
        double lat = Math.atan2(z, hyp);
        return new Point(lon * 180 / Math.PI, lat * 180 / Math.PI);
    }

}
